#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2016, OpenNebula Project, OpenNebula Systems                #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

nk_encoding = nil

if RUBY_VERSION =~ /^1.9/
    Encoding.default_external = Encoding::UTF_8
    Encoding.default_internal = Encoding::UTF_8
    nk_encoding = "UTF-8"
end

NOKOGIRI_ENCODING = nk_encoding

ONE_LOCATION = ENV["ONE_LOCATION"]

if !ONE_LOCATION
    LIB_LOCATION      = "/usr/lib/one"
    RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
    VAR_LOCATION      = "/var/lib/one"
    ETC_LOCATION      = "/etc/one"
    LOCK_FILE         = "/var/lock/one/one"
else
    LIB_LOCATION      = ONE_LOCATION + "/lib"
    RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
    VAR_LOCATION      = ONE_LOCATION + "/var"
    ETC_LOCATION      = ONE_LOCATION + "/etc"
    LOCK_FILE         = VAR_LOCATION + "/.lock"
end

$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/onedb'

require 'cli/command_parser'
require 'onedb'
require 'opennebula'

FORCE={
    :name => "force",
    :short => "-f",
    :large => "--force",
    :description => "Forces the backup even if the DB exists"
}

BACKUP={
    :name => "backup",
    :short => "-b file",
    :large => "--backup file",
    :description => "Use this file to store SQL dump",
    :format => String
}

###############################################################################
# SQLite options
###############################################################################
SQLITE={
    :name => "sqlite",
    :short => "-s file",
    :large => "--sqlite file",
    :format => String,
    :description => "SQLite DB file",
    :proc => lambda { |o, options|
        options[:backend] = :sqlite
        options[:sqlite]  = o
    }
}

###############################################################################
# MySQL options
###############################################################################
SERVER={
    :name => "server",
    :short => "-S host",
    :large => "--server host",
    :format => String,
    :description => "MySQL server hostname or IP. Defaults to localhost",
    :proc => lambda { |o, options|
        options[:backend] = :mysql
        options[:server]  = o
    }
}

PORT={
    :name => "port",
    :short => "-P port",
    :large => "--port port",
    :format => String,
    :description => "MySQL server port. Defaults to 3306",
    :proc => lambda { |o, options|
        options[:backend] = :mysql
        options[:port]  = o
    }
}

USERNAME={
    :name => "username",
    :short => "-u user",
    :large => "--username user",
    :format => String,
    :description => "MySQL username",
    :proc => lambda { |o, options|
        options[:backend] = :mysql
        options[:user]    = o
    }
}

PASSWORD={
    :name => "password",
    :short => "-p pass",
    :large => "--password pass",
    :format => String,
    :description => "MySQL password. Leave unset to be prompted for it",
    :proc => lambda { |o, options|
        options[:backend] = :mysql
        options[:passwd]  = o
    }
}

DBNAME={
    :name => "dbname",
    :short => "-d dbname",
    :large => "--dbname dbname",
    :format => String,
    :description => "MySQL DB name for OpenNebula",
    :proc => lambda { |o, options|
        options[:backend] = :mysql
        options[:db_name] = o
    }
}

###############################################################################
# Slave MySQL options
###############################################################################
SLAVE_SERVER={
    :name => "slave-server",
    :large => "--slave-server host",
    :format => String,
    :description => "Slave MySQL server hostname or IP. Defaults to localhost",
    :proc => lambda { |o, options|
        options[:slave_backend] = :mysql
        options[:slave_server]  = o
    }
}

SLAVE_PORT={
    :name => "slave-port",
    :large => "--slave-port port",
    :format => String,
    :description => "Slave MySQL server port. Defaults to 3306",
    :proc => lambda { |o, options|
        options[:slave_backend] = :mysql
        options[:slave_port]  = o
    }
}

SLAVE_USERNAME={
    :name => "slave-username",
    :large => "--slave-username user",
    :format => String,
    :description => "Slave MySQL username",
    :proc => lambda { |o, options|
        options[:slave_backend] = :mysql
        options[:slave_user]    = o
    }
}

SLAVE_PASSWORD={
    :name => "slave-password",
    :large => "--slave-password pass",
    :format => String,
    :description => "Slave MySQL password. Leave unset to be prompted for it",
    :proc => lambda { |o, options|
        options[:slave_backend] = :mysql
        options[:slave_passwd]  = o
    }
}

SLAVE_DBNAME={
    :name => "slave-dbname",
    :large => "--slave-dbname dbname",
    :format => String,
    :description => "Slave MySQL DB name for OpenNebula",
    :proc => lambda { |o, options|
        options[:slave_backend] = :mysql
        options[:slave_db_name] = o
    }
}

SLAVE_BACKUP={
    :name => "slave-backup",
    :large => "--slave-backup file",
    :description => "Use this file to store SQL dump",
    :format => String
}

###############################################################################
# Extra options
###############################################################################

EXTRA={
    :name => "extra",
    :large => "--extra arg",
    :description => "Extra args",
    :format => Array
}


cmd=CommandParser::CmdParser.new(ARGV) do
    description <<-EOT.unindent
        This command enables the user to manage the OpenNebula database. It
        provides information about the DB version, means to upgrade it to the
        latest version, and backup tools.
    EOT

    ###########################################################################
    # Global options
    ###########################################################################
    set :option, CommandParser::OPTIONS
    set :option, [SQLITE, SERVER, PORT, USERNAME, PASSWORD, DBNAME]

    ###########################################################################
    # Backup
    ###########################################################################
    backup_desc = <<-EOT.unindent
        Dumps the DB to a file specified in the argument
    EOT

    command :backup, backup_desc, [:output_file, nil], :options=>FORCE do
        begin
            helper = OneDB.new(options)
            helper.backup(args[0], options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # Version
    ###########################################################################
    version_desc = <<-EOT.unindent
        Prints the current DB version.
        Use -v flag to see also OpenNebula version
    EOT

    command :version , version_desc do
        begin
            helper = OneDB.new(options)
            helper.version(options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # History
    ###########################################################################
    history_desc = <<-EOT.unindent
        Prints the upgrades history
    EOT

    command :history , history_desc do
        begin
            helper = OneDB.new(options)
            helper.history
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # Restore
    ###########################################################################
    restore_desc = <<-EOT.unindent
        Restores the DB from a backup file. Only restores backups generated
        from the same backend (SQLite or MySQL)
    EOT

    command :restore , restore_desc, [:backup_file, nil], :options=>FORCE do
        begin
            helper = OneDB.new(options)
            helper.restore(args[0], options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # Upgrade
    ###########################################################################
    upgrade_desc = <<-EOT.unindent
        Upgrades the DB to the latest version
        where <version> : DB version (e.g. 1, 3) to upgrade.
                          By default the DB is upgraded to the latest version
    EOT

    command :upgrade , upgrade_desc, [:version, nil], :options=>[FORCE,BACKUP] do
        begin
            helper = OneDB.new(options)
            helper.upgrade(args[0], options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # fsck
    ###########################################################################
    fsck_desc = <<-EOT.unindent
        Checks the consistency of the DB, and fixes the problems found
    EOT

    command :fsck, fsck_desc, :options=>[FORCE,BACKUP] do
        begin
            helper = OneDB.new(options)
            helper.fsck(options)
        rescue Exception => e
            [-1, e.message]
        end
    end


    ###########################################################################
    # Import slave
    ###########################################################################
    import_slave_desc = <<-EOT.unindent
        Imports an existing federation slave into the federation master database
    EOT

    command :"import-slave", import_slave_desc, :options=>[FORCE,BACKUP,
        SLAVE_SERVER,SLAVE_PORT,SLAVE_USERNAME,SLAVE_PASSWORD,
        SLAVE_DBNAME,SLAVE_BACKUP] do

        begin
            helper = OneDB.new(options)
            helper.import_slave(options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # Patch
    ###########################################################################
    patch_desc = <<-EOT.unindent
        Applies a database patch file
    EOT

    command :patch , patch_desc, :file, :options=>[BACKUP, EXTRA] do
        begin
            helper = OneDB.new(options)
            helper.patch(args[0], options)
        rescue Exception => e
            [-1, e.message]
        end
    end

    ###########################################################################
    # Migrate SQLite to MySQL
    ###########################################################################
    sqlite2mysql_desc = <<-EOT.unindent
        Migrates a SQLite OpenNebula Database to MySQL
    EOT

    command :sqlite2mysql , sqlite2mysql_desc, :options=>[BACKUP] do
        begin
            options[:backend] = :sqlite
            sqlite = OneDB.new(options)

            options[:backend] = :mysql
            mysql = OneDB.new(options)

            mysql.sqlite2mysql(options, sqlite)
        rescue Exception => e
            [-1, e.message]
        end
    end
end
